package com.luozy.search.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.luozy.common.clients.ItemClient;
import com.luozy.common.dto.Item;
import com.luozy.common.dto.PageDTO;
import com.luozy.search.pojo.ItemDoc;
import com.luozy.search.pojo.RequestParams;
import com.luozy.search.service.SearchService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private RestHighLevelClient client;
    @Autowired
    private ItemClient itemClient;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    //1.ES基本搜索
    @Override
    public PageDTO<ItemDoc> search(RequestParams params) {
        try {
            //1.准备Request
            SearchRequest request = new SearchRequest("item");

            //2.搜索（布尔查询）
            BoolQueryBuilder boolQuery = getBoolQueryBuilder(params);

            //3.搜索（算分函数查询：广告竞价排名）
            FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                    //原始查询
                    boolQuery,
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                    //过滤条件
                                    QueryBuilders.termQuery("isAD", true),
                                    //算分函数，加权模式默认为相乘
                                    ScoreFunctionBuilders.weightFactorFunction(10)
                            )
                    }
            );

            request.source().query(functionScoreQuery);

            //4.高亮
            request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));

            //5.分页
            Integer page = params.getPage();
            Integer size = params.getSize();
            request.source().from((page - 1) * size).size(size);

            //6.排序
            String sortBy = params.getSortBy();
            if (sortBy.equals("sold")) {
                //按销量降序
                request.source().sort(sortBy, SortOrder.DESC);
            } else if (sortBy.equals("price")) {
                //按价格升序
                request.source().sort(sortBy, SortOrder.ASC);
            }

            //7.发送请求，获取响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //8.解析响应
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //布尔查询抽取
    private BoolQueryBuilder getBoolQueryBuilder(RequestParams params) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        /*①关键字搜索*/
        String key = params.getKey();
        if (key == null || key.equals("")) {
            //关键字为空则查询所有
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            //全文检索查询(name + category + brand)
            boolQuery.must(QueryBuilders.matchQuery("all", key));
        }

        /*②按分类 精确查询*/
        String category = params.getCategory();
        if (category != null && !category.equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("category", category));
        }

        /*③按品牌 精确查询*/
        String brand = params.getBrand();
        if (brand != null && !brand.equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("brand",brand));
        }

        /*④按价格 范围查询*/
        Integer minPrice = params.getMinPrice();
        Integer maxPrice = params.getMaxPrice();
        if (minPrice != null && maxPrice != null) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice * 100).lt(maxPrice * 100));
        }
        return boolQuery;
    }

    //解析响应
    private PageDTO<ItemDoc> handleResponse(SearchResponse response) throws JsonProcessingException {
        SearchHits searchHits = response.getHits();
        //获取总命中条数
        long total = searchHits.getTotalHits().value;
        //获取命中文档分页数组
        SearchHit[] hits = searchHits.getHits();
        //遍历数组，生成itemDoc集合
        List<ItemDoc> itemDocs = new ArrayList<>();
        for (SearchHit hit : hits) {
            //1、获取文档源数据
            String jsonString = hit.getSourceAsString();
            //转为itemDoc对象
            ItemDoc itemDoc = MAPPER.readValue(jsonString, ItemDoc.class);
            //2、获取高亮处理结果
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields.size() > 0 ) {
                // 根据字段名获取高亮结果
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    // 获取高亮值
                    String name = highlightField.getFragments()[0].string();
                    // 覆盖非高亮结果
                    itemDoc.setName(name);
                }
            }
            //放入HotelDoc集合
            itemDocs.add(itemDoc);
        }
        return new PageDTO<>(total, itemDocs);
    }

    //2.ES过滤项聚合
    @Override
    public Map<String, List<String>> filters(RequestParams params) {
        try {
            //1.准备Request
            SearchRequest request = new SearchRequest("item");
            //2.搜索条件
            BoolQueryBuilder boolQueryBuilder = getBoolQueryBuilder(params);
            request.source().query(boolQueryBuilder);
            //3.分类与品牌聚合
            request.source().size(0);//结果中不包含文档，只包含聚合结果
            request.source().aggregation(AggregationBuilders.terms("category_agg").field("category").size(20));
            request.source().aggregation(AggregationBuilders.terms("brand_agg").field("brand").size(50));
            //4.向ES发送请求，获取响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //5.解析聚合结果
            Map<String, List<String>> map = new HashMap<>();
            Aggregations aggregations = response.getAggregations();
            //分类聚合结果
            List<String> category = new ArrayList<>();
            Terms categoryAgg = aggregations.get("category_agg");
            //获取桶
            List<? extends Terms.Bucket> buckets1 = categoryAgg.getBuckets();
            for (Terms.Bucket bucket : buckets1) {
                //获取key，分类名称
                String categoryName = bucket.getKeyAsString();
                category.add(categoryName);
            }
            map.put("category", category);

            //品牌聚合结果
            List<String> brand = new ArrayList<>();
            Terms brandAgg = aggregations.get("brand_agg");
            //获取桶
            List<? extends Terms.Bucket> buckets2 = brandAgg.getBuckets();
            for (Terms.Bucket bucket : buckets2) {
                //获取key，品牌名称
                String brandName = bucket.getKeyAsString();
                brand.add(brandName);
            }
            map.put("brand", brand);

            return map;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //3.向ES添加商品（数据同步）
    @Override
    public void addItemById(Long id) {
        log.info("向ES添加id {} 商品",id);
        try {
            //远程调用数据库查询item
            Item item = itemClient.getById(id);
            //转为文档类型
            ItemDoc itemDoc = new ItemDoc();
            BeanUtils.copyProperties(item, itemDoc);

            IndexRequest request = new IndexRequest("item").id(id.toString());
            request.source(MAPPER.writeValueAsString(itemDoc), XContentType.JSON);
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //4.从ES删除商品（数据同步）
    @Override
    public void deleteItemById(Long id) {
        log.info("删除ES中id {} 商品",id);
        try {
            DeleteRequest request = new DeleteRequest("item", id.toString());
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
